package filesystem;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;

import util.common.AbstractManager;

/**
 * Class manages the asynchronous and synchronous loading of
 * files as a part of the asset pipeline.
 * 
 * @author Joel Rausch
 */
public class FileManager extends AbstractManager {
	
	/** File system object. */
	private FileSystem fileSystem;
	
	/** Set of included search paths. */
	private final Set<Path> searchPaths = new HashSet<Path>();
	
	/** Program directory. */
	private Path programPath;
	
	/** Name of the program directory. */
	private final String programDirName;
	
	/**
	 * Constructor taking the name of the program directory (not full path).
	 * @param programDirName
	 */
	public FileManager(String programDirName) {
		this.programDirName = programDirName;
	}
	
	@Override
	public void startup() throws Exception {
		fileSystem = FileSystems.getDefault();
		
		// Get base program directory
		DirectoryStream<Path> directory = Files.newDirectoryStream(Paths.get(this.getClass().getResource("").toURI()));
		Path programPath = directory.iterator().next();
		for(; programPath != null ;) {
			if(programPath.endsWith(Paths.get(programDirName))) {
				break;
			} else {
				programPath = programPath.getParent();
			}
		}

		this.programPath = programPath;
		searchPaths.add(programPath);
	}

	@Override
	public void shutdown() throws Exception {
		// Try to close file-system
		try {
			fileSystem.close();
		} catch(UnsupportedOperationException e) {
			// Pass
		}
	}
	
	/**
	 * Add a path relative to the program path. Each part of the path partitioned
	 * by path separator should occupy an index in the string array.
	 * 
	 * addSearchPath(new String[] {"res", "images"}) will add the path 
	 * ../ProgramDir/res/images to the set of search directories.
	 * 
	 * @param path
	 * @throws Exception
	 */
	public void addSearchPath(String[] relativePathParts) throws Exception {
		Path partial = Paths.get(programPath.toString(), relativePathParts);
		searchPaths.add(partial);
	}
	
	/**
	 * Returns a buffered text reader for the named file. Throws an exception if the
	 * file cannot be found or opened.
	 * 
	 * @param name Name of file.
	 * @return Returns a BufferedReader on the file.
	 * @throws IOException Thrown if the file cannot be found or opened.
	 */
	public BufferedReader textReader(String name) throws IOException {
		Path path = resolveName(name);
		if(path == null) {
			throw new IOException("Unable to resolve path");
		} else {
			return Files.newBufferedReader(path, Charset.defaultCharset());
		}
	}
	
	/**
	 * Returns a buffered text writer for a named, existing file. Throws an exception if the
	 * file cannot be found or opened.
	 * 
	 * @param name Name of file.
	 * @return Returns a BufferedWriter on the file.
	 * @throws IOException Thrown if file cannot be found or opened.
	 */
	public BufferedWriter textWriter(String [] name) throws IOException {
		Path path = Paths.get(programPath.toString(), name);
		
		// Create file if it doesn't exist
		if(!Files.exists(path)) {
			Files.createFile(path);
		}
		
		return Files.newBufferedWriter(path, Charset.defaultCharset());
	}
	
	/**
	 * Resolve the file name into a Path object.
	 * @param name Name of file.
	 * @return Return absolute path containing name.
	 * @throws IOException Throws exception if something funny happens.
	 */
	private Path resolveName(String name) throws IOException {
		// Iterate through search directories
		for(Path directory : searchPaths) {
			
			// Iterate through files in directory
			DirectoryStream<Path> dirStream = Files.newDirectoryStream(directory);
			for(Path file : dirStream) {
				
				// Compare file names
				if(file.getFileName().compareTo(Paths.get(name)) == 0) {
					return file;
				}
			}			
		}
		
		// Couldn't resolve file to a path
		return null;
	}
}
